import { extname, isAbsolute, join, resolve } from "path";
import { Func, Mutable } from "../../common/types";
import { hasProperty, stringEqual } from "../../common/util";
import { Diagnostic } from "../diagnostic";
import {
    computeLineAndCharacterOfLastLineEndPosition,
    computeLineAndCharacterOfPosition,
    computePositionOfLineAndCharacter
} from "./scanner";
import * as t from "./types";
import {
    forEachChild,
    getLineStarts,
    isBinaryOperatorToken,
    isDeclarationToken,
    isLiteralToken,
    isModifierToken,
    isNode,
    isPunctuationToken,
    isSignatureToken,
    isSymbolTable,
    isUnaryOperatorToken,
    mergeSymbolMapTo,
} from "./util";
import { readFileSync } from "../../common/system";

function findBaseType(baseTypes: t.Type[], checkType: t.Type): t.Type | undefined {
    for (const type of baseTypes) {
        if (type === checkType) {
            return type;
        }
        // 递归查找基类
        if (type.inherits) {
            let base = findBaseType(type.inherits, checkType);
            if (base) {
                return base;
            }
        }
    }
    return undefined;
}

class IntrinsicType implements t.IntrinsicType {

    public flag: t.TypeFlag;
    public name: string;
    public escapedName: string;
    public rank: number;
    public original?: t.LocalType | undefined;

    public constructor(type: t.IntrinsicType);
    public constructor(flag: t.TypeFlag, name: string, rank?: number);
    public constructor(arg: t.TypeFlag | t.IntrinsicType, name?: string, rank?: number) {
        if (typeof arg === "number") {
            this.flag = arg;
            this.name = name!;
            this.escapedName = name!.toLowerCase();
            this.rank = rank ?? 0;
        }
        else {
            this.flag = arg.flag;
            this.name = arg.name;
            this.escapedName = arg.escapedName;
            this.rank = arg.rank;
        }
    }

    public rankType(rank: number): t.IntrinsicType {
        if (rank <= 0) {
            return this;
        }
        let type = new IntrinsicType(this) as Mutable<t.IntrinsicType>;
        type.rank = rank;
        type.original = this.original ?? this;
        return type;
    }

    public is(flag: t.TypeFlag): boolean {
        return (flag & this.flag) > 0;
    }

    public isType(): this is t.Type {
        return hasProperty(this, "flag") && hasProperty(this, "symbol");
    }

    public isObjectType(): this is t.ObjectType {
        return this.isType() && hasProperty(this, "objectTypeFlag") && hasProperty(this, "members");
    }

    public compare(type: t.LocalType, allowInherit?: boolean, ignoreArray?: boolean): t.TypeCompareResult {
        // 如果两个对象相同，则直接返回
        if (this === type) {
            return { success: true, type };
        }
        // 检查当前对象和对比对象是否是基础类型
        // + 如果当前对象是any，则对比对象一定可以复制给当前对象
        if (this.is(t.TypeFlag.any) && this.rank === 0) {
            return { success: true, type };
        }
        // + 如果对比对象是any，则返回当前对象类型
        if (type.is(t.TypeFlag.any) && type.rank === 0) {
            return { success: true, type: this };
        }
        // enum -> long
        if ((this.isType() && this.symbol.is(t.SymbolKind.enum) && type === basicTypes.long) ||
            (type.isType() && type.symbol.is(t.SymbolKind.enum) && this === basicTypes.long)) {
            return { success: true, type: basicTypes.long };
        }
        // ignore array
        let success = ignoreArray || this.rank === type.rank;
        // True = 1, False = 0, Null = 0或者""
        if (this.is(t.TypeFlag.intrinsic) || type.is(t.TypeFlag.intrinsic)) {
            // 程序允许隐式转换为字符串，如果任意一个是字符串，应该返回字符串类型
            if (this.is(t.TypeFlag.stringLike) || type.is(t.TypeFlag.stringLike)) {
                return { success, type: basicTypes.string };
            }
            // 数字类型相关，double > long，boolean或null视为long
            if (this.is(t.TypeFlag.numberLike) && type.is(t.TypeFlag.numberLike)) {
                return {
                    success,
                    type: (this.is(t.TypeFlag.double) || type.is(t.TypeFlag.double)) ?
                        basicTypes.double : this.is(t.TypeFlag.boolean) ? basicTypes.boolean : basicTypes.long
                };
            }
        }
        // 引用类型可以赋值给`object`类型
        if (!this.isType() && this.is(t.TypeFlag.object) && type.isType()) {
            return { success, type };
        }
        // 如果都不是any，检查两个类型是否有继承关系，如果有继承关系，需要分两种情况检查：
        // 1. `allowInherit`参数为true时，应当视为作为参数传入函数时的类型检查，不需要抛出错误，将返回派生类。
        // 2. `allowInherit`参数为false时，只要两个类型不同，即抛出错误，将返回any类型。
        if (allowInherit && this.isType() && type.isType()) {
            let success = this === type;
            let t = type;
            if (!success && t.inherits) {
                let checkInheritResult = findBaseType(t.inherits, this);
                if (!checkInheritResult) {
                    return { success: false, type: basicTypes.any };
                }
                return { success: true, type: checkInheritResult };
            }
        }
        return { success: false, type: basicTypes.any };
    }

    public isCompatible(type: t.IntrinsicType, ignoreArray = false): boolean {
        // any 对于所有类型都兼容
        if (this.is(t.TypeFlag.any)) {
            // 检查数组类型
            if ((this.flag & t.TypeFlag.arrayType) === (type.flag & t.TypeFlag.arrayType)) {
                return true;
            }
            return ignoreArray;
        }
        // object 对于所有引用类型都是兼容的
        if (this.is(t.TypeFlag.object) && type.isType() && type.symbol.is(t.SymbolKind.reference)) {
            // 检查数组类型
            if ((this.flag & t.TypeFlag.arrayType) === (type.flag & t.TypeFlag.arrayType)) {
                return true;
            }
            return ignoreArray;
        }
        // 对于其余状况：
        // 由于类型声明只会在遇到`class`、`interface`、`enum`时才会创建，并且原始对象不会再进行改变，
        // 此时，如果类型对象通过`rankType`方法创建时，其`original`应始终指向同一个对象，因此，
        // 单纯比较原始对象是否相同即可。
        const checkThis = this.original ?? this;
        const checkType = type.original ?? this;
        return checkThis === checkType && (ignoreArray || checkThis.rank === checkType.rank);
    }

}

class Type extends IntrinsicType implements t.Type {

    public id: number;
    public symbol: t.Symbol;
    public inherits: t.Type[];
    public implements: t.Type[];

    public constructor(type: t.Type);
    public constructor(id: number, flag: t.TypeFlag, name: string, symbol: t.Symbol);
    public constructor(arg: number | t.Type, flag?: t.TypeFlag, name?: string, symbol?: t.Symbol) {
        super(t.TypeFlag.any, "");
        if (typeof arg === "number") {
            this.id = arg;
            this.flag = flag!;
            this.name = name!;
            this.escapedName = name!.toLowerCase();
            this.symbol = symbol!;
            this.inherits = [];
            this.implements = [];
        }
        else {
            this.id = arg.id;
            this.flag = arg.flag;
            this.name = arg.name;
            this.escapedName = arg.escapedName;
            this.symbol = arg.symbol;
            this.inherits = arg.inherits.slice();
            this.implements = arg.implements.slice();
        }
    }

    public override rankType(rank: number): t.Type {
        let type = new Type(this) as Mutable<t.Type>;
        type.rank = rank;
        type.original = this.original ?? this;
        return type;
    }

}

export const basicTypes = {
    undefined: new IntrinsicType(t.TypeFlag.undefined, "undefined"),
    void: new IntrinsicType(t.TypeFlag.void, "void"),
    any: new IntrinsicType(t.TypeFlag.any, "any"),
    string: new IntrinsicType(t.TypeFlag.string, "string"),
    boolean: new IntrinsicType(t.TypeFlag.boolean, "boolean"),
    null: new IntrinsicType(t.TypeFlag.null, "null"),
    long: new IntrinsicType(t.TypeFlag.long, "long"),
    double: new IntrinsicType(t.TypeFlag.double, "double"),
    date: new IntrinsicType(t.TypeFlag.date, "date"),
    categorical: new IntrinsicType(t.TypeFlag.categorical, "categorical"),
    object: new IntrinsicType(t.TypeFlag.object, "object"),
};

class SymbolTracker implements t.SymbolTracker {

    private _nodeMap: Map<number, {
        node: t.Node,
        type: t.LocalType
    }>;
    private _latest: t.LocalType;
    private _nodeTypeChanged?: t.SymbolTypeUpdatedEventHandler;

    constructor(type?: t.LocalType) {
        this._nodeMap = new Map();
        this._latest = type ?? basicTypes.any;
    }

    public latest(): t.LocalType {
        return this._latest;
    }

    public appendNode(node: t.Node): void {
        this._nodeMap.set(node.id, {
            node,
            type: this._latest
        });
    }

    public getNodeType(node: t.Node): t.LocalType | undefined {
        return this._nodeMap.get(node.id)?.type;
    }

    public getTypeAt(position: number): t.LocalType | undefined {
        let temp: t.LocalType | undefined;
        for (const [, { node, type }] of this._nodeMap) {
            if (node.end < position) {
                temp = type;
            }
            else {
                break;
            }
        }
        return temp;
    }

    public setNodeType(node: t.Node, type: t.LocalType): void {
        this._nodeMap.set(node.id, { node, type });
        this._latest = type;
        if (this._nodeTypeChanged) {
            this._nodeTypeChanged(type, node);
        }
    }

    public forEachNode(callback: (node: t.Node) => void): void {
        this._nodeMap.forEach(({ node }) => callback(node));
    }

    public onNodeTypeChanged(handler: t.SymbolTypeUpdatedEventHandler): void {
        this._nodeTypeChanged = handler;
    }

}

class SymbolTable implements t.SymbolTable {

    _symbolTableBrand: any;

    private _positions: t.SymbolPositionElement[];

    private _declares?: Map<string, t.Symbol>;
    private _macros?: Map<string, t.Symbol>;
    private _metadatas?: Map<string, t.Symbol>;
    private _sections?: Map<string, t.Symbol>;

    private _locals: Map<string, t.Symbol>;
    private _functions: Map<string, t.Symbol>;

    public constructor() {
        this._positions = [];
        this._locals = new Map();
        this._functions = new Map();
        this._symbolTableBrand = undefined;
    }

    public get size(): number {
        return this._locals.size + this._functions.size +
            (this._declares ? this._declares.size : 0) +
            (this._macros ? this._macros.size : 0) +
            (this._metadatas ? this._metadatas.size : 0) +
            (this._sections ? this._sections.size : 0);
    }

    private insertToPosition(arg: t.Symbol | t.Symbol[], position?: number | undefined) {
        let pos: number;
        let macros: Map<string, t.Symbol> = new Map();
        let local: Map<string, t.Symbol> = new Map();
        if (Array.isArray(arg)) {
            if (arg.length === 0) {
                return;
            }
            pos = position !== undefined ? position : arg[0].declaration.end;
            for (const item of arg) {
                if (item.declaration.end > pos && position === undefined) {
                    pos = item.declaration.end;
                }
                if (item.is(t.SymbolKind.macro)) {
                    macros.set(item.escapedName, item);
                }
                else if (item.is(t.SymbolKind.localDeclarations)) {
                    local.set(item.escapedName, item);
                }
            }
        }
        else {
            pos = position !== undefined ? position : arg.declaration.end;
            if (arg.is(t.SymbolKind.macro)) {
                macros.set(arg.escapedName, arg);
            }
            else if (arg.is(t.SymbolKind.localDeclarations)) {
                local.set(arg.escapedName, arg);
            }
        }

        if (pos >= 0 && (macros.size > 0 || local.size > 0)) {

            let item: t.SymbolPositionElement = {
                position: pos,
                macros,
                local
            };

            let insertIndex = this._positions.findIndex(element => element.position > pos);
            if (insertIndex >= 0) {
                for (let i = 0; i < insertIndex; i++) {
                    mergeSymbolMapTo(undefined, macros, this._positions[i].macros);
                    mergeSymbolMapTo(undefined, local, this._positions[i].local);
                }
                for (let i = insertIndex; i < this._positions.length; i++) {
                    const element = this._positions[i];
                    mergeSymbolMapTo(undefined, element.macros as t.SymbolMap, macros);
                    mergeSymbolMapTo(undefined, element.local as t.SymbolMap, local);
                }
                this._positions.splice(insertIndex, 0, item);
            }
            else {
                if (this._positions.length > 0) {
                    const last = this._positions[this._positions.length - 1];
                    mergeSymbolMapTo(undefined, macros, last.macros);
                    mergeSymbolMapTo(undefined, local, last.local);
                }
                this._positions.push(item);
            }
        }
    }

    private updateMap(map: Map<string, t.Symbol>, symbol: t.Symbol, onConflict?: Func<t.Symbol, void>) {
        if (map.has(symbol.escapedName)) {
            onConflict?.(symbol);
        }
        else {
            map.set(symbol.escapedName, symbol);
        }
    }

    private setSymbol(arg: t.Symbol, scope?: t.SymbolScope, onConflict?: Func<t.Symbol, void>): void {
        if (scope !== undefined) {
            if (!!(scope & t.SymbolScope.local)) {
                this.updateMap(this._locals, arg, onConflict);
            }
            else if (!!(scope & t.SymbolScope.function)) {
                this.updateMap(this._functions, arg, onConflict);
            }
            else if (!!(scope & t.SymbolScope.macro)) {
                this.updateMap(this._macros || (this._macros = new Map()), arg, onConflict);
            }
            else if (!!(scope & t.SymbolScope.declare)) {
                this.updateMap(this._declares || (this._declares = new Map()), arg, onConflict);
            }
            else if (!!(scope & t.SymbolScope.section)) {
                this.updateMap(this._sections || (this._sections = new Map()), arg, onConflict);
            }
            else if (!!(scope & t.SymbolScope.metadata)) {
                this.updateMap(this._metadatas || (this._metadatas = new Map()), arg, onConflict);
            }
        }
        else {
            this.updateMap(this._locals, arg, onConflict);
        }
    }

    public set(symbols: t.Symbol[], scope?: t.SymbolScope | undefined, onConflict?: Func<t.Symbol, void>): void;
    public set(symbol: t.Symbol, scope?: t.SymbolScope | undefined, onConflict?: Func<t.Symbol, void>): void;
    public set(arg: t.Symbol | t.Symbol[], scope?: t.SymbolScope, onConflict?: Func<t.Symbol, void>): void {
        if (Array.isArray(arg)) {
            for (const s of arg) {
                this.setSymbol(s, scope, onConflict);
            }
        }
        else {
            this.setSymbol(arg, scope, onConflict);
        }
        this.insertToPosition(arg);
    }

    public delete(name: string, scope?: t.SymbolScope, position?: number): boolean {
        let lowerName = name.toLowerCase();
        let result: boolean = false;
        let deleteMacro = false;
        if (scope !== undefined) {
            if (scope === undefined || !!(scope & t.SymbolScope.local)) {
                result = this._locals.delete(lowerName);
            }
            if (scope === undefined || !!(scope & t.SymbolScope.function)) {
                result = this._functions.delete(lowerName);
            }
            if ((scope === undefined || !!(scope & t.SymbolScope.macro)) && this._macros) {
                deleteMacro = (result = this._macros.delete(name));
            }
            if ((scope === undefined ||!!(scope & t.SymbolScope.declare)) && this._declares) {
                result = this._declares.delete(lowerName);
            }
            if ((scope === undefined ||!!(scope & t.SymbolScope.section)) && this._sections) {
                result = this._sections.delete(lowerName);
            }
            if ((scope === undefined ||!!(scope & t.SymbolScope.metadata)) && this._metadatas) {
                result = this._metadatas.delete(lowerName);
            }
        }
        if (deleteMacro && position && this._positions.length > 0) {
            let last = this._positions[this._positions.length - 1];
            let macros = new Map(last.macros.entries());
            macros.delete(name);
            this._positions.push({
                position,
                macros,
                local: new Map(last.local.entries())
            });
        }
        return result;
    }

    public clear(): void {
        this._locals.clear();
        this._functions.clear();
        this._declares?.clear();
        this._macros?.clear();
        this._metadatas?.clear();
        this._sections?.clear();
    }

    public get(arg: string | string[], scope?: t.SymbolScope): t.Symbol | undefined {

        let result: t.Symbol | undefined;

        if (Array.isArray(arg)) {
            if (arg.length > 0 && this._declares) {
                let topLevel = arg[0];
                result = this.get(topLevel, t.SymbolScope.declare);
                let index = 1;
                while (result && index < arg.length) {
                    result = result.members.get(arg[index].toLowerCase()) || result.locals.get(arg[index].toLowerCase());
                    index++;
                }
            }
            return result;
        }

        let lowerName = arg.toLowerCase();
        if (scope !== undefined) {
            if (!!(scope & t.SymbolScope.local)) {
                result = this._locals.get(lowerName);
            }
            if (!result && !!(scope & t.SymbolScope.function)) {
                result = this._functions.get(lowerName);
            }
            if (!result && !!(scope & t.SymbolScope.macro)) {
                result = this._macros?.get(arg);
            }
            if (!result && !!(scope & t.SymbolScope.declare)) {
                result = this._declares?.get(lowerName);
            }
            if (!result && !!(scope & t.SymbolScope.section)) {
                result = this._sections?.get(lowerName);
            }
            if (!result && !!(scope & t.SymbolScope.metadata)) {
                result = this._metadatas?.get(lowerName);
            }
            return result;
        }

        return this._locals.get(lowerName) ||
            this._functions.get(lowerName) ||
            this._macros?.get(arg) ||
            this._declares?.get(lowerName) ||
            this._sections?.get(lowerName) ||
            this._metadatas?.get(lowerName);

    }

    public has(name: string): boolean {
        let lowerName = name.toLowerCase();
        return this._locals.has(lowerName) ||
            this._functions.has(lowerName) ||
            !!this._macros?.has(name) ||
            !!this._declares?.has(lowerName) ||
            !!this._sections?.has(lowerName) ||
            !!this._metadatas?.has(lowerName);
    }

    public values(scope: t.SymbolScope | undefined): t.Symbol[] {
        let result: t.Symbol[] = [];
        if (scope === undefined || !!(scope & t.SymbolScope.local)) {
            result.push(...this._locals.values());
        }
        if (scope === undefined || !!(scope & t.SymbolScope.function)) {
            result.push(...this._functions.values());
        }
        if (scope === undefined || !!(scope & t.SymbolScope.macro)) {
            result.push(...(this._macros ? this._macros.values() : []));
        }
        if (scope === undefined || !!(scope & t.SymbolScope.declare)) {
            result.push(...(this._declares ? this._declares.values() : []));
        }
        if (scope === undefined || !!(scope & t.SymbolScope.metadata)) {
            result.push(...(this._metadatas ? this._metadatas.values() : []));
        }
        if (scope === undefined || !!(scope & t.SymbolScope.section)) {
            result.push(...(this._sections ? this._sections.values() : []));
        }
        return result;
    }

    public forEach(callback: (s: t.Symbol, scope: t.SymbolScope) => any, scope?: t.SymbolScope, thisArg?: any): void {
        if (scope === undefined || !!(scope & t.SymbolScope.local)) {
            for (const [, local] of this._locals) {
                callback.call(thisArg, local, t.SymbolScope.local);
            }
        }
        if (scope === undefined || !!(scope & t.SymbolScope.function)) {
            for (const [, func] of this._functions) {
                callback.call(thisArg, func, t.SymbolScope.function);
            }
        }
        if ((scope === undefined || !!(scope & t.SymbolScope.macro)) && this._macros) {
            for (const [, macro] of this._macros) {
                callback.call(thisArg, macro, t.SymbolScope.macro);
            }
        }
        if ((scope === undefined || !!(scope & t.SymbolScope.declare)) && this._declares) {
            for (const [, dec] of this._declares) {
                callback.call(thisArg, dec, t.SymbolScope.declare);
            }
        }
        if ((scope === undefined || !!(scope & t.SymbolScope.section)) && this._sections) {
            for (const [, sec] of this._sections) {
                callback.call(thisArg, sec, t.SymbolScope.section);
            }
        }
        if ((scope === undefined || !!(scope & t.SymbolScope.metadata)) && this._metadatas) {
            for (const [, meta] of this._metadatas) {
                callback.call(thisArg, meta, t.SymbolScope.metadata);
            }
        }
    }

    public insert(insertTable: t.ReadonlySymbolTable, position: number, onConflict?: Func<t.Symbol, void>): void;
    public insert(insertTable: t.ReadonlySymbolMap, position: number, scope?: t.SymbolScope, onConflict?: Func<t.Symbol, void>): void;
    public insert(arg1: t.ReadonlySymbolTableLike, arg2: number, arg3?: t.SymbolScope | Func<t.Symbol, void>, arg4?: number | Func<t.Symbol, void>): void {

        let symbols: t.Symbol[] = [];
        let position: number;

        let insertSymbol = (s: t.Symbol, scope: t.SymbolScope, cb?: Func<t.Symbol, void>) => {
            if (this.has(s.escapedName) && (scope & t.SymbolScope.macro) === 0) {
                cb?.(s);
            }
            else {
                let name = s.escapedName;
                if (!!(scope & t.SymbolScope.local)) {
                    this._locals.set(name, s);
                    symbols.push(s);
                }
                if (!!(scope & t.SymbolScope.function)) {
                    this._functions.set(name, s);
                }
                if (!!(scope & t.SymbolScope.declare)) {
                    (this._declares || (this._declares = new Map())).set(name, s);
                }
                if (!!(scope & t.SymbolScope.macro)) {
                    (this._macros || (this._macros = new Map())).set(name, s);
                    symbols.push(s);
                }
                if (!!(scope & t.SymbolScope.section)) {
                    (this._sections || (this._sections = new Map())).set(name, s);
                }
                if (!!(scope & t.SymbolScope.metadata)) {
                    (this._metadatas || (this._metadatas = new Map())).set(name, s);
                }
            }
        };

        if (isSymbolTable(arg1)) {
            position = arg2;
            arg1.forEach((s, scope) => insertSymbol(s, scope, arg3 as Func<t.Symbol, void>));
        }
        else {
            let scope = arg3 !== undefined ? arg3 as t.SymbolScope : undefined;
            position = arg2;
            arg1.forEach(s => {
                if (scope === undefined) {
                    if (s.declaration.isSignature()) {
                        scope = t.SymbolScope.declare;
                    }
                    else if (s.is(t.SymbolKind.localDeclarations)) {
                        scope = t.SymbolScope.local;
                    }
                    else if (s.is(t.SymbolKind.function)) {
                        scope = t.SymbolScope.function;
                    }
                    else if (s.is(t.SymbolKind.macro)) {
                        scope = t.SymbolScope.macro;
                    }
                    else if (s.is(t.SymbolKind.metadata)) {
                        scope = t.SymbolScope.metadata;
                    }
                    else if (s.is(t.SymbolKind.section)) {
                        scope = t.SymbolScope.section;
                    }
                    else {
                        scope = t.SymbolScope.local;
                    }
                }
                insertSymbol(s, scope, arg4 as Func<t.Symbol, void>);
            });
        }

        if (symbols.length > 0 && position >= 0) {
            this.insertToPosition(symbols, position);
        }
    }

    public positionAt(position: number): readonly t.Symbol[] {

        let positionElement: t.SymbolPositionElement | undefined;
        for (const ele of this._positions) {
            if (ele.position <= position) {
                positionElement = ele;
            }
            else {
                break;
            }
        }
        return positionElement ? Array.from(positionElement.local.values()).concat(Array.from(positionElement.macros.values())) : [];
    }

    public table(scope: t.SymbolScope): t.ReadonlySymbolMap {
        if (!!(scope & t.SymbolScope.function)) {
            return this._functions;
        }
        if (!!(scope & t.SymbolScope.macro)) {
            return this._macros || (this._macros = new Map());
        }
        if (!!(scope & t.SymbolScope.declare)) {
            return this._declares || (this._declares = new Map());
        }
        if (!!(scope & t.SymbolScope.section)) {
            return this._sections || (this._sections = new Map());
        }
        if (!!(scope & t.SymbolScope.metadata)) {
            return this._metadatas || (this._metadatas = new Map());
        }
        return this._locals || (this._locals = new Map());
    }

    public reference(table: t.ReadonlySymbolMap, scope: t.SymbolScope) {
        if (!!(scope & t.SymbolScope.local)) {
            this._locals = table as t.SymbolMap;
        }
        else if (!!(scope & t.SymbolScope.function)) {
            this._locals = table as t.SymbolMap;
        }
        else if (!!(scope & t.SymbolScope.declare)) {
            this._functions = table as t.SymbolMap;
        }
        else if (!!(scope & t.SymbolScope.macro)) {
            this._macros = table as t.SymbolMap;
        }
        else if (!!(scope & t.SymbolScope.section)) {
            this._sections = table as t.SymbolMap;
        }
        else if (!!(scope & t.SymbolScope.metadata)) {
            this._sections = table as t.SymbolMap;
        }
    }

}

class Symbol implements t.Symbol {

    public id: number;
    public name: string;
    public escapedName: string;
    public kind: t.SymbolKind;
    public checkFlag: t.CheckFlag;
    public literalType?: t.LiteralType;
    public sectionKind: t.SectionKind;
    public locals: t.ReadonlySymbolMap;
    public members: t.ReadonlySymbolMap;
    public defaultMember: t.Symbol | undefined;
    public parent: t.Symbol | undefined;
    public declaration: t.SymbolDecl;
    public parameters: t.Symbol[];
    public tracker: t.SymbolTracker;
    public indexDimensions: number[];
    public categories: t.Symbol | undefined;
    public initialization: string | number | boolean | undefined;
    public mdmKind: t.SymbolMDMKind;

    public constructor(symbol: t.Symbol);
    public constructor(id: number, name: string, flag: t.SymbolKind, declaration?: t.SymbolDecl, type?: t.LocalType);
    public constructor(arg: number | t.Symbol, name?: string, flag?: t.SymbolKind, declaration?: t.SymbolDecl, type?: t.LocalType) {
        if (typeof arg === "number") {
            this.id = arg;
            this.name = name!;
            this.kind = flag!;
            this.escapedName = this.is(t.SymbolKind.macro) ? name! : name!.toLowerCase();
            this.declaration = declaration!;
            this.checkFlag = t.CheckFlag.none;
            this.sectionKind = t.SectionKind.none;
            this.mdmKind = t.SymbolMDMKind.none;
            this.locals = new Map();
            this.members = new Map();
            this.parent = undefined;
            this.parameters = [];
            this.tracker = new SymbolTracker(type);
            this.indexDimensions = [];
        }
        else {
            this.id = arg.id;
            this.name = arg.name;
            this.kind = arg.kind;
            this.escapedName = arg.escapedName;
            this.declaration = arg.declaration;
            this.checkFlag = arg.checkFlag;
            this.sectionKind = arg.sectionKind;
            this.mdmKind = arg.mdmKind;
            this.locals = arg.locals;
            this.members = arg.members;
            this.parent = arg.parent;
            this.parameters = arg.parameters;
            this.tracker = arg.tracker;
            this.indexDimensions = arg.indexDimensions;
        }
    }

    public get type(): t.LocalType {
        return this.tracker.latest();
    }

    public is(flag: t.SymbolKind): boolean {
        return !!(this.kind & flag);
    }

    public isCheckFlag(flag: t.CheckFlag): boolean {
        return !!(this.checkFlag & flag);
    }

    public isSection(flag: t.SectionKind): boolean {
        return this.is(t.SymbolKind.section) && this.sectionKind === flag;
    }

    public isMDM(flag: t.SymbolMDMKind): boolean {
        return this.is(t.SymbolKind.metadata) && this.mdmKind === flag;
    }

    public set<P extends keyof t.SymbolBase>(propName: P, value: t.SymbolBase[P]): void {
        switch (propName) {
            case "kind":
                this.kind = value as t.SymbolKind;
                break;
            case "checkFlag":
                this.checkFlag = value as t.CheckFlag;
                break;
            case "defaultMember":
                this.defaultMember = value as t.Symbol;
                break;
            case "initialization":
                this.initialization = value as string | number | boolean;
                break;
            case "parent":
                this.parent = value as t.Symbol;
                break;
            case "literalType":
                this.literalType = value as t.LiteralType;
                break;
            case "categories":
                this.categories = value as t.Symbol;
                break;
            case "sectionKind":
                this.sectionKind = value as t.SectionKind;
                break;
            case "mdmKind":
                this.mdmKind = value as t.SymbolMDMKind;
                break;
        }
    }

    public getDefaultProperty(): t.Symbol {
        let result: t.Symbol = this;
        while (true) {
            if (result.defaultMember) {
                result = result.defaultMember;
            }
            else {
                let type = result.type;
                if (type.isType() && type.symbol.defaultMember) {
                    result = type.symbol.defaultMember;
                }
                else {
                    break;
                }
            }
        }
        return result;
    }

    public getMaybeDefaultProperty(predicate: t.Predicate<t.Symbol>): t.Symbol | undefined {
        let result: t.Symbol = this;
        while (true) {
            if (predicate(result)) {
                return result;
            }
            if (result.defaultMember) {
                result = result.defaultMember;
            }
            else {
                let type = result.type;
                if (type.isType() && type.symbol.defaultMember) {
                    result = type.symbol.defaultMember;
                    continue;
                }
                break;
            }
        }
    }

}

class Node implements t.Node {

    public id: number;
    public aheadComments: t.Comment[];
    public kind: t.SyntaxKind;
    public parent?: t.Node;
    public symbol?: t.Symbol;
    public locals?: t.SymbolTable;
    public text: string;
    public optionFlag: t.OptionFlag;
    public optionNode?: t.OptionStatement;
    public start: number;
    public end: number;
    public expands: t.MacroExpandToken[];
    private _children: t.Node[];

    constructor();
    constructor(node: t.Node);
    constructor(flag: t.SyntaxKind, start: number, end: number);
    constructor(arg?: t.SyntaxKind | t.Node, start?: number, end?: number) {
        if (isNode(arg)) {
            this.kind = arg.kind;
            this.start = arg.start;
            this.end = arg.end;
            this.id = arg.id;
            this.aheadComments = arg.aheadComments.slice();
            this.text = arg.text;
            this.optionFlag = arg.optionFlag;
            this.expands = arg.expands;
            this._children = arg.children as t.Node[];
        }
        else {
            this.kind = arg !== undefined ? arg : t.SyntaxKind.unknown;
            this.start = start !== undefined ? start : -1;
            this.end = end !== undefined ? end : -1;
            this.id = 0;
            this.aheadComments = [];
            this.text = "";
            this.optionFlag = t.OptionFlag.explicit;
            this.expands = [];
            this._children = [];
        }
    }

    public asParent(parent?: t.Node): void {
        if (this.parent) {
            this.parent.removeChild(node => node.id === this.id);
        }
        if (parent) {
            parent.asChildren(this);
        }
        else {
            this.parent = undefined;
        }
    }

    public getParent(predicate?: t.Predicate<t.Node>): Node | undefined;
    public getParent<T extends Node>(predicate: t.Predicate<t.Node>): T | undefined;
    public getParent<T extends Node>(predicate?: t.Predicate<t.Node>): T | undefined {
        if (!predicate) {
            return this.parent as T;
        }
        let result = this.parent;
        while (result && !predicate(result)) {
            result = result.parent;
        }
        return result as T;
    }

    public setSymbol(symbol: t.Symbol): void {
        this.symbol = symbol;
    }

    public getSourceFile(): t.SourceFile {
        let node = this.parent;
        while (node && node.kind !== t.SyntaxKind.sourceFile) {
            node = node.parent;
        }
        return node as t.SourceFile;
    }

    public getWidth(): number {
        return this.end - this.start;
    }

    public childrenCount(): number {
        return this._children.length;
    }

    public get children(): readonly t.Node[] {
        return this._children;
    }

    public childAt(index: number): t.Node {
        return this._children[index];
    }

    public asChildren(...children: readonly (t.Node | undefined)[]) {
        for (const child of children) {
            if (child) {
                this._children.push(child);
                (child as Mutable<t.Node>).parent = this;
            }
        }
    }

    public removceAllChildren(): void {
        for (const child of this._children) {
            (child as Mutable<t.Node>).parent = undefined;
        }
        this._children.splice(0);
    }

    public removeChild(predicate: t.Predicate<t.Node>): void {
        if (this._children.length === 0) {
            return;
        }
        let findIndex = this._children.findIndex(predicate);
        while (findIndex > -1) {
            (this.childAt(findIndex) as Mutable<t.Node>).parent = undefined;
            this._children.splice(findIndex, 1);
            findIndex = this._children.findIndex(predicate);
        }
    }

    public forEachChild<T>(nodeCallback: (node: t.Node) => T, nodeArrayCallback?: (nodes: t.NodeArray<t.Node>) => T): T | undefined {
        return forEachChild(this, nodeCallback, nodeArrayCallback);
    }

    public is<T extends t.Node>(kind: T["kind"]): this is T {
        return this.kind === kind;
    }

    public isToken<T extends t.SyntaxKind>(kind: T): this is t.Token<T> {
        return this.kind === kind;
    }

    public isModifierToken(): this is t.ModifierToken<t.DeclareModifierKind>;
    public isModifierToken<T extends t.DeclareModifierKind>(flag: T): this is t.ModifierToken<T>;
    public isModifierToken(kind?: t.DeclareModifierKind): boolean {
        return kind === undefined ? isModifierToken(this.kind) : kind === this.kind;
    }

    public isPunctuation(): this is t.Punctuation<t.PunctuationKind>;
    public isPunctuation<T extends t.PunctuationKind>(flag: T): this is t.Punctuation<T>;
    public isPunctuation(kind?: t.PunctuationKind): boolean {
        return kind === undefined ? isPunctuationToken(this.kind) : kind === this.kind;
    }

    public isUnaryOperatorToken(): this is t.Token<t.UnaryOperatorKind>;
    public isUnaryOperatorToken<T extends t.UnaryOperatorKind>(flag: T): this is t.Token<T>;
    public isUnaryOperatorToken(kind?: t.UnaryOperatorKind): boolean {
        return kind === undefined ? isUnaryOperatorToken(this.kind) : kind === this.kind;
    }

    public isBinaryOperatorToken(): this is t.Token<t.BinaryOperatorKind>;
    public isBinaryOperatorToken<T extends t.BinaryOperatorKind>(flag: T): this is t.Token<T>;
    public isBinaryOperatorToken(kind?: t.BinaryOperatorKind): boolean {
        return kind === undefined ? isBinaryOperatorToken(this.kind) : kind === this.kind;
    }

    public isSourceFile(): this is t.SourceFile {
        return this.kind === t.SyntaxKind.sourceFile;
    }

    public isIdentifier(): this is t.Identifier {
        return this.kind === t.SyntaxKind.identifier;
    }

    public isStringLiteral(): this is t.StringLiteral {
        return this.kind === t.SyntaxKind.stringLiteral;
    }

    public isNumberLiteral(): this is t.NumberLiteral {
        return this.kind === t.SyntaxKind.numberLiteral;
    }

    public isCategoricalLiteral(): this is t.CategoricalLiteral {
        return this.kind === t.SyntaxKind.categoricalLiteral;
    }

    public isTrueLiteral(): this is t.TrueLiteral {
        return this.kind === t.SyntaxKind.trueLiteral;
    }

    public isFalseLiteral(): this is t.FalseLiteral {
        return this.kind === t.SyntaxKind.falseLiteral;
    }

    public isBooleanLiteral(): this is t.TrueLiteral | t.FalseLiteral {
        return this.isTrueLiteral() || this.isFalseLiteral();
    }

    public isNullLiteral(): this is t.NullLiteral {
        return this.kind === t.SyntaxKind.nullLiteral;
    }

    public isLiteral(): this is t.Literal {
        return isLiteralToken(this.kind);
    }

    public isExpression(): this is t.Expression;
    public isExpression<T extends t.Expression>(kind: T["kind"]): this is T;
    public isExpression(kind?: t.SyntaxKind): boolean {
        return hasProperty(this, "_expressionBrand") && (kind === undefined || kind === this.kind);
    }

    public isAssignmentExpression(): this is t.AssignmentExpression {
        return this.isExpression() && this.kind === t.SyntaxKind.binaryExpr && hasProperty(this, "_assignmentBrand");
    }

    public isMemberExpressionObject(): this is t.MemberExpressionObject {
        return this.kind === t.SyntaxKind.identifier
            || this.kind === t.SyntaxKind.memberExpr
            || this.kind === t.SyntaxKind.callExpr
            || this.kind === t.SyntaxKind.indexExpr;
    }

    public isCallee(): this is t.Callee {
        return this.kind === t.SyntaxKind.identifier
            || this.kind === t.SyntaxKind.memberExpr;
    }

    public isSignature(): this is t.Signature<t.SignatureKind>;
    public isSignature<T extends t.Signature<t.SignatureKind>>(flag: T["kind"]): this is T;
    public isSignature(kind?: t.SignatureKind): boolean {
        return kind === undefined ? isSignatureToken(this.kind) : kind === this.kind;
    }

    public isDeclaration(): this is t.Declaration;
    public isDeclaration<T extends t.Declaration>(kind: T["kind"]): this is T;
    public isDeclaration(kind?: t.DeclarationKind): boolean {
        return kind === undefined ? isDeclarationToken(this.kind) : kind === this.kind;
    }

    public isStatement(): this is t.Statement;
    public isStatement<T extends t.Statement>(kind: T["kind"]): this is T;
    public isStatement(kind?: t.SyntaxKind): boolean {
        return hasProperty(this, "_statementBrand") && (kind === undefined || kind === this.kind);
    }

    public isPreprocessor(): this is t.Preprocessor;
    public isPreprocessor<T extends t.Preprocessor>(kind: T["kind"]): this is T;
    public isPreprocessor(kind?: t.PPStatementKind): boolean {
        return hasProperty(this, "_preprocessorBrand") && (kind === undefined || kind === this.kind);
    }

    public isMissingNode(): boolean {
        return this.kind !== t.SyntaxKind.eof && this.start === this.end && this.start !== 0;
    }

    public isMDMCollection(): this is t.MDMCollection<t.MDMLabeledObject> {
        return hasProperty(this, "_metadataCollectionBrand");
    }

    public isMDMField(): this is t.MDMField {
        return this.kind === t.SyntaxKind.mdmField;
    }

    public isMDMDbQuestions(): this is t.MDMDbQuestions {
        return this.isMDMField() && hasProperty(this, "_metadataDBBrand");
    }

    public isMDMPage(): this is t.MDMPage {
        return this.isMDMField() && hasProperty(this, "_metadataPageBrand");
    }

}

class Identifier extends Node implements t.Identifier {

    public kind: t.SyntaxKind.identifier = t.SyntaxKind.identifier;
    public _expressionBrand: any;

    constructor();
    constructor(node: t.Node);
    constructor(kind: t.SyntaxKind.identifier, start: number, end: number);
    constructor(arg?: t.Node | t.SyntaxKind.identifier, start?: number, end?: number) {
        super();
        this.kind = t.SyntaxKind.identifier;
        if (isNode(arg)) {
            this.start = arg.start;
            this.end = arg.end;
            this.id = arg.id;
            this.aheadComments = arg.aheadComments.slice();
            this.text = arg.text;
            this.optionFlag = arg.optionFlag;
            this.asChildren(...arg.children);
        }
        if (start !== undefined) {
            this.start = start;
        }
        if (end !== undefined) {
            this.end = end;
        }
    }

    public override forEachChild<T>(_nodeCallback: (node: t.Node) => T, _nodeArrayCallback?: ((nodes: t.NodeArray<t.Node>) => T) | undefined): T | undefined {
        return undefined;
    }

}

class MetadataArealLike<T extends t.MDMArealBase> extends Node implements t.MDMArealLike<T> {

    public kind: t.MDMTokenKind;
    public areaName?: t.Identifier;
    public areaObjects: Map<string, T>;

    constructor(flag: t.MDMTokenKind, start: number, end: number) {
        super(flag, start, end);
        this.kind = flag;
        this.areaObjects = new Map();
    }

    public override forEachChild<T>(nodeCallback: (node: t.Node) => T, _?: ((nodes: t.NodeArray<t.Node>) => T) | undefined): T | undefined {
        for (const [_, value] of this.areaObjects) {
            let result = nodeCallback(value);
            if (result) {
                return result;
            }
        }
    }

    public areaObjectCount(): number {
        return this.areaObjects.size;
    }

    public forEachObject(callback: (obj: T, area: string) => void): void {
        this.areaObjects.forEach((v, k) => callback(v, k));
    }

    public getAreaObject(areaName: string = "") {
        return this.areaObjects.get(areaName.toLowerCase());
    }

    public addArealObject(items: T[], replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void;
    public addArealObject(item: T, replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void;
    public addArealObject(arg: T | T[], replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void {
        if (Array.isArray(arg)) {
            for (const item of arg) {
                this.addArealObject(item, replaceWhenExist, conflictCallback);
            }
            return;
        }
        if (!arg.areaName || arg.areaName.text === "") {
            return;
        }
        let areaName = arg.areaName.text;
        let exist = this.areaObjects.get(areaName);
        if (exist) {
            if (replaceWhenExist) {
                this.areaObjects.set(areaName, arg);
                this.removeChild(node => node.id === exist?.id);
            }
            if (conflictCallback) {
                conflictCallback(exist, arg);
            }
        }
        else {
            this.areaObjects.set(areaName, arg);
        }
        this.asChildren(arg);
    }

    public merge(newObjects: t.MDMArealLike<T>, replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void {
        newObjects.forEachObject(obj => this.addArealObject(obj, replaceWhenExist, conflictCallback));
    }

}

class MetadataPropertiesLike<T extends t.MDMKeyValuePair, U extends t.MDMDeclaration> extends MetadataArealLike<U> implements t.MDMPropertiesLike<T, U> {

    public kind: U["kind"];
    public items: Map<string, T>;

    constructor(flag: U["kind"], start: number, end: number) {
        super(flag, start, end);
        this.kind = flag;
        this.items = new Map();
    }

    public override forEachChild<T>(nodeCallback: (node: t.Node) => T, _?: ((nodes: t.NodeArray<t.Node>) => T) | undefined): T | undefined {
        for (const [_, value] of this.items) {
            let result = nodeCallback(value);
            if (result) {
                return result;
            }
        }
        for (const [_, value] of this.areaObjects) {
            let result = nodeCallback(value);
            if (result) {
                return result;
            }
        }
    }

    public itemsCount(): number {
        return this.items.size;
    }

    public forEachItem(callback: (item: T) => void) {
        this.items.forEach(item => callback(item));
    }

    public getItem(name: string): T | undefined {
        return this.items.get(name.toLowerCase());
    }

    public addItem(item: T, replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void {
        let lowerName = item.name.text.toLowerCase();
        let exist = this.items.get(lowerName);
        if (exist) {
            if (replaceWhenExist) {
                this.items.set(lowerName, item);
                this.removeChild(node => node.id === exist?.id);
            }
            if (conflictCallback) {
                conflictCallback(exist, item);
            }
        }
        else {
            this.items.set(lowerName, item);
        }
        this.asChildren(item);
    }

    public addItems(items: readonly T[], replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void;
    public addItems(items: ReadonlyMap<string, T>, replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void;
    public addItems(items: readonly T[] | ReadonlyMap<string, T>, replaceWhenExist?: true, conflictCallback?: t.MDMConflictCallback<T>): void {
        items.forEach(v => this.addItem(v, replaceWhenExist, conflictCallback));
    }

}

class MetadataStyle extends MetadataArealLike<t.MDMStyle> implements t.MDMStyle {

    public kind: t.SyntaxKind.mdmStyle = t.SyntaxKind.mdmStyle;
    public name: t.Identifier;
    public control?: t.MDMControlStyle;
    public cell?: t.MDMCellStyle;
    public font?: t.MDMFontStyle;
    public color?: t.MDMProperty;
    public bgColor?: t.MDMProperty;
    public hidden?: t.MDMProperty;
    public align?: t.MDMProperty;
    public verticalAlign?: t.MDMProperty;
    public elementAlign?: t.MDMProperty;
    public indent?: t.MDMProperty;
    public zIndex?: t.MDMProperty;
    public cursor?: t.MDMProperty;
    public image?: t.MDMProperty;
    public imagePosition?: t.MDMProperty;
    public orientation?: t.MDMProperty;
    public columns?: t.MDMProperty;
    public rows?: t.MDMProperty;
    public width?: t.MDMProperty;
    public height?: t.MDMProperty;
    public _declarationBrand: any;
    public _statementBrand: any;
    public bindingType?: t.Binding | undefined;
    public reference?: t.Node | undefined;

    public constructor(flag: t.SyntaxKind.mdmStyle, start: number, end: number, name: t.Identifier) {
        super(flag, start, end);
        this.name = name;
    }

    public set(property: [ keyof t.MDMStyleProperties, t.MDMProperty ]): void;
    public set(control?: t.MDMControlStyle, cell?: t.MDMCellStyle, font?: t.MDMFontStyle): void;
    public set(object: t.MDMStyleProperties): void;
    public set(object: t.MDMStyle): void;
    public set(arg1?: t.MDMControlStyle | t.MDMStyle | t.MDMStyleProperties | [ keyof t.MDMStyleProperties, t.MDMProperty ], arg2?: t.MDMCellStyle, arg3?: t.MDMFontStyle) {
        if (!arg1) {
            return;
        }
        if (Array.isArray(arg1)) {
            this[arg1[0]] = arg1[1];
            this.asChildren(arg1[1]);
        }
        else if (isNode(arg1)) {
            if (arg1.kind === t.SyntaxKind.mdmStyle) {
                if (arg1.control) {
                    this.control = arg1.control;
                }
                if (arg1.cell) {
                    this.cell = arg1.cell;
                }
                if (arg1.font) {
                    this.font = arg1.font;
                }
                let properties = arg1 as t.MDMStyleProperties;
                let prop: keyof t.MDMStyleProperties;
                for (prop in properties) {
                    if (properties[prop]) {
                        this[prop] = properties[prop];
                        this.asChildren(properties[prop]);
                    }
                }
            }
            else {
                this.control = arg1 as t.MDMControlStyle;
                this.asChildren(this.control);
                if (arg2) {
                    this.cell = arg2 as t.MDMCellStyle;
                    this.asChildren(this.cell);
                }
                if (arg3) {
                    this.font = arg3 as t.MDMFontStyle;
                    this.asChildren(this.font);
                }
            }
        }
        else {
            const param = arg1 as t.MDMStyleProperties;
            let key: keyof t.MDMStyleProperties;
            for (key in param) {
                if (param[key]) {
                    this[key] = param[key];
                    this.asChildren(param[key]);
                }
            }
        }
    }

}

class MetadataCollection<T extends t.MDMLabeledObject> extends Node implements t.MDMCollection<T> {

    public kind: t.MDMTokenKind;
    _metadataCollectionBrand: any;

    private _items: T[];

    public constructor(flag: t.MDMTokenKind, start: number, end: number) {
        super(flag, start, end);
        this.kind = flag;
        this._items = [];
        this._metadataCollectionBrand = undefined;
    }

    get count(): number {
        return this._items.length;
    }

    get elements(): readonly T[] {
        return this._items;
    }

    public override forEachChild<T>(nodeCallback: (node: t.Node) => T, _?: ((nodes: t.NodeArray<t.Node>) => T) | undefined): T | undefined {
        for (const item of this._items) {
            let result = nodeCallback(item);
            if (result) {
                return result;
            }
        }
    }

    public forEachElement(callback: (element: T, index: number) => void): void {
        if (this._items.length > 0) {
            this._items.forEach((value, i) => {
                callback(value, i);
            });
        }
    }

    public getElement(name: string): T | undefined {
        return this._items.find(e => stringEqual(name, e.name.text));
    }

    public getElementAt(index: number): T {
        return this._items[index];
    }

    public removeElement(name: string): void {
        let index = this._items.findIndex(e => stringEqual(e.name.text, name));
        if (index > -1) {
            this._items[index].asParent(undefined);
            this._items.splice(index, 1);
        }
    }

    public removeElementAt(index: number): void {
        if (index >= 0 && index < this._items.length) {
            this._items.splice(index, 1);
        }
    }

    private updateRange(element: T) {
        if (element.start >= 0 && element.start < this.start) {
            this.start = element.start;
        }
        if (element.end > this.end) {
            this.end = element.end;
        }
    }

    public add(element: T): void;
    public add(element: T[]): void;
    public add(arg: T | T[]): void {
        if (Array.isArray(arg)) {
            for (const element of arg) {
                this._items.push(element);
                this.asChildren(element);
                this.updateRange(element);
            }
            return;
        }
        this._items.push(arg);
        this.asChildren(arg);
        this.updateRange(arg);
    }

}

class SourceFile extends Node implements t.SourceFile {

    public kind: t.SyntaxKind.sourceFile = t.SyntaxKind.sourceFile;
    public fileName: string;
    public fileKind: t.FileKind;
    public references: t.SourceFile[];
    public includes: t.PPInclude[];
    public statements!: t.NodeArray<t.Statement>;
    public metadata?: t.MDMDocument;
    public eof!: t.Token<t.SyntaxKind.eof>;
    public diagnostics: Diagnostic[];
    public lineMap: readonly number[];
    public declarations: t.FunctionDeclaration[];
    public signatures: t.Signature<t.SignatureKind>[];
    public locals: t.SymbolTable;
    public macros: Map<string, Symbol>;
    public sections: readonly t.Section[];
    public commentFlag: t.CommentType;
    public name: t.Identifier;
    public bindingType?: t.Binding;
    public reference?: t.Node;
    public comments: t.Comment[];
    public locations: { nodes: t.LineLocation[]; symbols: Map<string, Symbol>; };
    public identifiers: t.Identifier[];
    public macroNodes!: t.NodeArray<t.Identifier>;
    _declarationBrand: any;
    _statementBrand: any;

    constructor(flag: t.SyntaxKind.sourceFile, start: number, end: number) {
        super(flag, start, end);
        this.fileName = "";
        this.fileKind = t.FileKind.none;
        this.references = [];
        this.includes = [];
        this.diagnostics = [];
        this.lineMap = [];
        this.declarations = [];
        this.signatures = [];
        this.locals = new SymbolTable();
        this.macros = new Map();
        this.sections = [];
        this.commentFlag = t.CommentType.none;
        this.name = new Identifier();
        this.comments = [];
        this.locations = {
            nodes: [],
            symbols: new Map()
        };
        this.identifiers = [];
    }

    public update(newText: string): void {
        this.text = newText;
        this.lineMap = getLineStarts(this);
    }

    public getLineAndCharacterOfLastLineEndPosition(position: number): t.TextPosition {
        return computeLineAndCharacterOfLastLineEndPosition(this.lineMap, position);
    }

    public getLineAndCharactorOfPosition(position: number): t.TextPosition {
        return computeLineAndCharacterOfPosition(this.lineMap, position);
    }

    public getPositionOfLineAndCharacter(line: number, character: number): number {
        return computePositionOfLineAndCharacter(this.lineMap, line, character);
    }

    public getCommentAtPosition(position: number): t.Comment | undefined {
        if (this.comments.length === 0) {
            return undefined;
        }
        // binary search
        let low = 0;
        let high = this.comments.length - 1;
        while (low <= high) {
            let mid = low + ((high - low) >> 1);
            let comment = this.comments[mid];
            if (comment.start < position && position < comment.end) {
                return comment;
            }
            // 搜索位置在中值左边
            else if (comment.start > position) {
                high = mid - 1;
            }
            // 搜索位置在中值右边
            else {
                low = mid + 1;
            }
        }
    }

}

class Project {

    private _root: string;
    private _activeFiles: Map<string, t.File>;
    private _mrsFiles: Map<string, t.File>;
    private _dmsFiles: Map<string, t.File>;
    private _iniFiles: Map<string, t.File>;
    private _batFiles: Map<string, t.File>;
    private _mddFiles: Map<string, t.File>;
    private _declareFiles: Map<string, t.File>;
    private _metadataFiles: Map<string, t.File>;
    private _routingFiles: Map<string, t.File>;

    public constructor(root?: string) {
        this._root = root || "";
        this._activeFiles = new Map();
        this._mrsFiles = new Map();
        this._dmsFiles = new Map();
        this._iniFiles = new Map();
        this._batFiles = new Map();
        this._mddFiles = new Map();
        this._declareFiles = new Map();
        this._metadataFiles = new Map();
        this._routingFiles = new Map();
    }

    public asAbsolutePath(...args: string[]): string {
        let path = join(...args);
        if (isAbsolute(path)) {
            return path;
        }
        return resolve(this._root, path);
    }

    public load(root: string): void {
        this._root = root;
    }

    public clear(): void {
        this._root = "";
        this._activeFiles.clear();
        this._mrsFiles.clear();
        this._dmsFiles.clear();
        this._iniFiles.clear();
        this._batFiles.clear();
        this._mddFiles.clear();
        this._declareFiles.clear();
        this._metadataFiles.clear();
        this._routingFiles.clear();
    }

    public getFile(path: string): t.File | undefined {
        const lowerPath = path.toLowerCase();
        return this._mrsFiles.get(lowerPath)
            || this._dmsFiles.get(lowerPath)
            || this._batFiles.get(lowerPath)
            || this._mddFiles.get(lowerPath)
            || this._declareFiles.get(lowerPath)
            || this._metadataFiles.get(lowerPath)
            || this._routingFiles.get(lowerPath)
            ;
    }

    public getSourceFile(path: string, entryPath?: string): t.SourceFile | undefined {
        return this.getFile(path)?.getSourceFile(entryPath);
    }

    public activate(path: string): t.File | undefined {
        let lowerPath = path.toLowerCase();
        const file = this.getFile(lowerPath);
        if (file) {
            this._activeFiles.set(lowerPath, file);
        }
        return file;
    }

    public isFileActive(path: string): boolean {
        return this._activeFiles.has(path.toLowerCase());
    }

    private getFileMap(kind: t.FileKind): Map<string, t.File> | undefined {
        switch (kind) {
            case t.FileKind.mrs:       return this._mrsFiles;
            case t.FileKind.dms:       return this._dmsFiles;
            case t.FileKind.ini:       return this._iniFiles;
            case t.FileKind.bat:       return this._batFiles;
            case t.FileKind.mdd:       return this._mddFiles;
            case t.FileKind.declare:   return this._declareFiles;
            case t.FileKind.metadata:  return this._metadataFiles;
            case t.FileKind.routing:   return this._routingFiles;
        }
    }

    public createFile(path: string, content?: string): t.File {
        const file = new File(this, path, content);
        this.getFileMap(file.kind)?.set(path.toLowerCase(), file);
        return file;
    }

    public deleteFile(path: string): t.File | undefined {
        const lowerPath = path.toLowerCase();
        const file = this.getFile(lowerPath);
        if (file) {
            return file;
        }
    }



}

class FileTracker implements t.FileTracker {

    private _file: t.File;
    private _branches: Map<string, t.FileBranch>;
    private _lockedBranch: t.FileBranch | undefined;
    private _parents: t.FileReference[];
    private _children: t.FileReference[];
    private _latest: t.SourceFile | undefined;

    public constructor(file: t.File) {
        this._file = file;
        this._branches = new Map();
        this._parents = [];
        this._children = [];
    }

    public get file() { return this._file; }
    public get branches() { return Array.from(this._branches.values()).sort((a, b) => a.depth - b.depth); }
    public get parents() { return this._parents.slice(); }
    public get children() { return this._children.slice(); }

    private asMember(fileRef: t.FileReference, collection: t.FileReference[]) {
        let obj = collection.find(e => e.file === fileRef.file);
        if (obj) {
            (obj as Mutable<t.FileReference>).directive = fileRef.directive;
            (obj as Mutable<t.FileReference>).commentReference = fileRef.commentReference;
        }
        else {
            collection.push(fileRef);
        }
    }

    public asParent(file: t.File, directive: t.FileDirective | undefined, commentReference: boolean, span: t.TextSpan): void {
        this.asMember({ file, directive, commentReference, span }, this._parents);
    }

    public asChild(file: t.File, directive: t.FileDirective | undefined, commentReference: boolean, span: t.TextSpan): void {
        this.asMember({ file, directive, commentReference, span }, this._children);
    }

    public getParent(path: string): t.FileReference | undefined {
        return this._parents.find(e => stringEqual(e.file.path, path));
    }

    public getChild(path: string): t.FileReference | undefined {
        return this._children.find(e => stringEqual(e.file.path, path));
    }

    public isParent(file: t.File): boolean {
        return !!this._parents.find(e => e.file === file);
    }

    public isChild(file: t.File): boolean {
        return !!this._children.find(e => e.file === file);
    }

    private removeMember(arg: string | t.File | t.Predicate<t.File>, collection: t.FileReference[]) {
        let findIndex: number;
        if (typeof arg === "string") {
            findIndex = collection.findIndex(e => stringEqual(e.file.path, arg));
        }
        else if (typeof arg === "function") {
            findIndex = collection.findIndex(e => arg(e.file));
        }
        else {
            findIndex = collection.findIndex(e => e.file === arg);
        }
        if (findIndex > -1) {
            let removed = collection[findIndex];
            collection.splice(findIndex, 1);
            return removed;
        }
    }

    public removeParent(path: string): void;
    public removeParent(file: t.File): void;
    public removeParent(func: t.Predicate<t.File>): void;
    public removeParent(arg: string | t.File | t.Predicate<t.File>): void {
        let removed = this.removeMember(arg, this._parents);
        if (removed && removed.file.isEntry()) {
            this._branches.delete(removed.file.path.toLowerCase());
        }
    }

    public clearParents(): void {
        this._parents.splice(0);
    }

    public removeChild(path: string): void;
    public removeChild(file: t.File): void;
    public removeChild(func: t.Predicate<t.File>): void;
    public removeChild(arg: string | t.File | t.Predicate<t.File>): void {
        this.removeMember(arg, this._children);
    }

    public clearChildren(): void {
        this._children.splice(0);
    }

    public lockBranch(entryPath: string): t.FileBranch | undefined {
        let branch = this._branches.get(entryPath.toLowerCase());
        if (branch) {
            this._lockedBranch = branch;
        }
        return branch;
    }

    public asBranch(entry: t.File, depth: number): void {
        this._branches.set(
            entry.path.toLowerCase(),
            {
                entry,
                depth,
                source: undefined
            }
        );
    }

    public getEntry(): t.File;
    public getEntry(path: string): t.File | undefined;
    public getEntry(path?: string): t.File | undefined {
        if (!path) {
            return this._branches.size === 0 ? this._file : this.getBranch().entry;
        }
        return this._branches.get(path.toLowerCase())?.entry;
    }

    public getBranch(): t.FileBranch;
    public getBranch(path: string): t.FileBranch | undefined;
    public getBranch(path?: string): t.FileBranch | undefined {
        if (!path) {
            return this._lockedBranch ? this._lockedBranch : this._branches.size > 0 ? this.branches[0] : undefined;
        }
        return this._branches.get(path.toLowerCase());
    }

    public updateBranch(source: t.SourceFile, entryPath?: string): void {
        let branch: t.FileBranch | undefined;
        if (entryPath) {
            branch = this.getBranch(entryPath);
        }
        else {
            branch = this.getBranch();
        }
        if (branch) {
            (branch as Mutable<t.FileBranch>).source = source;
        }
        this._latest = source;
    }

    public getSourceFile(): t.SourceFile | undefined;
    public getSourceFile(entryPath: string): t.SourceFile | undefined;
    public getSourceFile(entryPath?: string): t.SourceFile | undefined {
        if (entryPath) {
            return this.getBranch(entryPath)?.source;
        }
        let localBranches = this.branches;
        return this._lockedBranch ?
            this._lockedBranch.source :
            (this._latest ?? (localBranches.length > 0 ? localBranches[0].source : undefined));
    }

}

class File implements t.File {

    public kind: t.FileKind;
    public tracker: t.FileTracker;
    public directives: t.FileDirective[];
    public version: number;

    private _content: string;

    public constructor(
        public readonly project: Project,
        public readonly path: string,
        content?: string
    ) {
        this.tracker = new FileTracker(this);
        this.directives = [];
        this.version = 0;

        this._content = content ?? readFileSync(this.path);

        this.kind = t.FileKind.none;
        const lowerPath = this.path.toLowerCase();
        if (lowerPath.endsWith(t.GenerateFileExtens.declare)) {
            this.kind = t.FileKind.declare;
        }
        else if (lowerPath.endsWith(t.GenerateFileExtens.metadata)) {
            this.kind = t.FileKind.mdd;
        }
        else if (lowerPath.endsWith(t.GenerateFileExtens.routing)) {
            this.kind = t.FileKind.routing;
        }
        else {
            switch (extname(lowerPath)) {
                case ".mrs":
                    this.kind = t.FileKind.mrs;
                    break;
                case ".dms":
                    this.kind = t.FileKind.dms;
                    break;
                case ".ini":
                    this.kind = t.FileKind.ini;
                    break;
                case ".bat":
                    this.kind = t.FileKind.bat;
                    break;
                case ".mdd":
                    this.kind = t.FileKind.mdd;
                    break;
            }
        }
    }

    public get content() { return this._content; }
    public set content(value) { this._content = value; }

    private clearReferenceParent() {
        for (const dir of this.directives) {
            this.tracker.removeChild(dir.fileName);
        }
        this.directives.splice(0);
    }

    private clearParent() {
        this.clearReferenceParent();
        // 清空已有的父子对象关系
        for (const parent of this.tracker.parents) {
            parent.file.tracker.removeChild(this);
        }
        this.tracker.clearParents();
    }

    private clearChildren() {
        for (const child of this.tracker.children) {
            child.file.tracker.removeParent(this);
        }
        this.tracker.clearChildren();
    }

    public init(): void {
        throw new Error("Method not implemented.");
    }

    public update(param: t.FileContentUpdateParam): void {
        throw new Error("Method not implemented.");
    }

    getProgram(): t.Program {
        throw new Error("Method not implemented.");
    }
    isEntry(): boolean {
        throw new Error("Method not implemented.");
    }
    isLeaf(): boolean {
        throw new Error("Method not implemented.");
    }
    getParent(predicate: t.Predicate<t.File>): t.File | undefined {
        throw new Error("Method not implemented.");
    }
    getSourceFile(entryPath: string | undefined): t.SourceFile | undefined {
        throw new Error("Method not implemented.");
    }
    updateSourceFile(sourceFile: t.SourceFile, entryPath?: string | undefined): void {
        throw new Error("Method not implemented.");
    }
    traverse(callback: Func<t.FileReference, void>, stack?: t.File[] | undefined): void {
        throw new Error("Method not implemented.");
    }



}

export const objectAllocator: t.ObjectAllocator = {
    getIntrinsicTypeConstructor: () => IntrinsicType,
    getTypeConstructor: () => Type,
    getSymbolConstructor: () => Symbol,
    getNodeConstructor: () => Node,
    getNodeCopyConstructor: () => Node,
    getIdentifierConstructor: () => Identifier,
    getMetadataArealLikeConstructor: () => MetadataArealLike,
    getMetadataPropertiesLikeConstructor: () => MetadataPropertiesLike,
    getMetadataStyleConstructor: () => MetadataStyle,
    getMetadataCollectionConstructor: () => MetadataCollection,
    getSourceFileConstructor: () => SourceFile,
    getSymbolTableConstructor: () => SymbolTable,
};


